import { HttpException, HttpStatus, Injectable } from '@nestjs/common'
import { CreateMenuDto, CreateMenuPermissionDto } from './dto/create-menu.dto'
import {
  BindOperationDto,
  UpdateMenuDto,
  UpdateMenuPermissionDto
} from './dto/update-menu.dto'
import { InjectRepository } from '@nestjs/typeorm'
import {
  Menu,
  MenuOperation,
  RoleMenu,
  MenuPermission
} from './entities/menu.entity'
import { QueryFailedError, Repository } from 'typeorm'
import { BindRoleMenuDto } from '../role/dto/create-role.dto'
import { HttpExceptionEnum } from 'types/http-exception'
import { OperationService } from '../operation/operation.service'
import { Operation } from '../operation/entities/operation.entity'

@Injectable()
export class MenuService {
  constructor(
    @InjectRepository(Menu)
    private readonly menuRepository: Repository<Menu>,
    @InjectRepository(RoleMenu)
    private readonly roleMenuRepository: Repository<RoleMenu>,
    @InjectRepository(MenuOperation)
    private readonly menuOperationRepository: Repository<MenuOperation>,
    private readonly operationService: OperationService,
    @InjectRepository(MenuPermission)
    private readonly menuPermissionRepository: Repository<MenuPermission>
  ) {}
  async create(createMenuDto: CreateMenuDto) {
    try {
      const menu = new Menu(createMenuDto)
      return await this.menuRepository.save(menu)
    } catch (error) {
      if (error instanceof QueryFailedError) {
        throw new HttpException(
          HttpExceptionEnum.MENU_EXISTED,
          HttpStatus.BAD_REQUEST
        )
      }
      throw error
    }
  }

  async findAll() {
    return await this.menuRepository.find()
  }

  async findOne(id: number) {
    const menu = await this.menuRepository.findOneBy({ id })
    if (!menu)
      throw new HttpException(
        HttpExceptionEnum.MENU_NOT_EXIST,
        HttpStatus.BAD_REQUEST
      )
    return menu
  }

  async update(updateMenuDto: UpdateMenuDto) {
    const menu = await this.findOne(updateMenuDto.id)
    if (!menu) {
      throw new HttpException(
        HttpExceptionEnum.MENU_NOT_EXIST,
        HttpStatus.BAD_REQUEST
      )
    }
    Object.assign(menu, updateMenuDto)

    return this.menuRepository.save(menu)
  }

  async remove(id: number) {
    return await this.menuRepository.delete(id)
  }

  async findMenuTreeByRoleId(roleId: number, relation: boolean = true) {
    const queryBuilder = this.roleMenuRepository
      .createQueryBuilder('roleMenu')
      .where('roleMenu.role_id = :roleId', { roleId })
      .andWhere('roleMenu.status = 1')

    let roleMenus = []
    if (relation) {
      roleMenus = await queryBuilder
        .select([
          'roleMenu.role_id AS roleId',
          'roleMenu.menu_id AS menuId',
          'roleMenu.operations AS operations',
          'menu.name AS name',
          'menu.description AS description',
          'menu.parent_id AS parentId',
          'menu.icon AS icon',
          'menu.path AS path',
          'menu.component AS component',
          'roleMenu.permissions AS permissions'
        ])
        .leftJoin('roleMenu.menu', 'menu')
        .getRawMany()
    } else {
      roleMenus = await queryBuilder.getMany()
    }

    return roleMenus
  }

  /**
   * 根据角色id和菜单树绑定角色菜单关联
   * @param {roleId} 角色id
   * @param {tree} 菜单树
   */
  async bindRoleMenu(bindRoleMenu: BindRoleMenuDto) {
    const { roleId, tree } = bindRoleMenu
    try {
      tree.forEach(async (menu) => {
        this.traverseTree(roleId, menu)
      })
    } catch (error) {
      console.log(
        error,
        '根据角色id和菜单树绑定角色菜单关联',
        '--------bindRoleMenu()'
      )
      throw new HttpException(
        HttpExceptionEnum.MENU_NOT_EXIST,
        HttpStatus.BAD_REQUEST
      )
    }
  }

  /**
   * 根据角色id和菜单id删除角色菜单关联
   * @param roleId 角色id
   * @param menuId 菜单id
   * @returns
   */
  async removeRoleMenu(roleId: number, menuId: number) {
    return await this.roleMenuRepository.delete({ roleId, menuId })
  }

  async traverseTree(roleId: number, menu: any) {
    // 处理当前节点的逻辑
    console.log(`Processing node with id: ${menu.name}`)

    try {
      const operations =
        menu.operations instanceof Array ? menu.operations.join(',') : ''
      const formatMenu = Object.assign(menu, { roleId, operations })
      const roleMenu = new RoleMenu(formatMenu)

      this.roleMenuRepository.save(roleMenu)
    } catch (error) {
      console.log(error, '处理当前节点的逻辑', '--------traverseTree()')
      throw error
    }
    // 检查当前节点是否有 children
    if (menu.children && menu.children.length > 0) {
      // 递归遍历每个子节点
      menu.children.forEach((child: any) => {
        this.traverseTree(roleId, child)
      })
    }
  }

  /**
   * @description 根据menuId查询菜单绑定可选的操作
   * @param menuId
   * @returns {Array<Operation>}
   */
  async findCanOperationsByMenuId(menuId: number): Promise<Operation[]> {
    const [operationData, canOperations] = await Promise.all([
      this.operationService.findAll(),
      this.menuOperationRepository.findOneBy({
        menuId
      })
    ])
    const operations = canOperations?.operations
      ? canOperations.operations.split(',')
      : []
    const canOperationCodes = new Set(operations)
    const canOperationList = operationData.list.filter((v) =>
      canOperationCodes.has(v.code)
    )
    return canOperationList
  }

  /**
   * @description 根据roleId, menuId查询菜单分配的操作
   * @param menuId 菜单id
   * @param roleId 角色id
   * @returns Array<Operation.code>
   */
  async findBindOperationsByMenuId(
    roleId: number,
    menuId: number
  ): Promise<string[]> {
    const bindMenuOpeation = await this.roleMenuRepository.findOneBy({
      roleId,
      menuId
    })
    if (!bindMenuOpeation?.operations) return []
    return bindMenuOpeation.operations.split(',')
  }

  /**
   * @description 根据menuId查询菜单绑定可选的操作id
   * @param menuId
   * @returns Array<operation.code>
   */
  async findCanOperationIdsByMenuId(menuId: number) {
    try {
      const menuOpeation = await this.menuOperationRepository.findOneBy({
        menuId
      })
      // 表里没数据则没绑定给空数组
      if (!menuOpeation?.operations) {
        return []
      }

      return menuOpeation.operations.split(',')
    } catch (error) {
      console.log('findCanOperationIdsByMenuId----menu.service')
      console.log(error)
    }
  }

  async bindMenuOpeation(bindDto: BindOperationDto) {
    const { menuId, operationCodes } = bindDto
    // 先判断operationIds是不是数组，不是数组则转换为数组
    const menuOpeation = new MenuOperation({
      menuId,
      operations: operationCodes.join(',')
    })

    return await this.menuOperationRepository.save(menuOpeation)
  }

  async findMenuPermission(menuId: number, keyword?: string) {
    const queryBuilder =
      this.menuPermissionRepository.createQueryBuilder('menuPermission')
    queryBuilder.where('menuPermission.menu_id = :menuId', { menuId })
    if (keyword) {
      queryBuilder.where(
        'menuPermission.name LIKE :keyword OR menuPermission.permission LIKE :keyword',
        {
          keyword: `%${keyword}%`
        }
      )
    }
    return await queryBuilder.getMany()
  }

  /**
   * @description 根据roleId, menuId查询菜单绑定的接口权限
   * @param menuId 菜单id
   * @param roleId 角色id
   * @returns Array<MenuPermission.permissions>
   */
  async findBindPermissionByMenuId(
    roleId: number,
    menuId: number
  ): Promise<string[]> {
    const binds = await this.roleMenuRepository.findOneBy({
      roleId,
      menuId
    })
    if (!binds?.permissions) return []
    return binds.permissions.split(',')
  }

  async createMenuPermission(createMenuPermission: CreateMenuPermissionDto) {
    try {
      const newPermission = new MenuPermission(createMenuPermission)
      return await this.menuPermissionRepository.save(newPermission)
    } catch (error) {
      if (error instanceof QueryFailedError) {
        throw new HttpException(
          HttpExceptionEnum.PERMISSION_EXISTED,
          HttpStatus.BAD_REQUEST
        )
      }
      throw error
    }
  }

  async updateMenuPermission(updateMenuPermission: UpdateMenuPermissionDto) {
    const menuPermission = await this.menuPermissionRepository.findOneBy({
      id: updateMenuPermission.id
    })

    if (!menuPermission) {
      throw new HttpException(
        HttpExceptionEnum.PERMISSION_NOT_FOUND,
        HttpStatus.NOT_FOUND
      )
    }
    Object.assign(menuPermission, updateMenuPermission)
    return await this.menuPermissionRepository.save(menuPermission)
  }

  async removeMenuPermission(id: number) {
    return await this.menuPermissionRepository.delete(id)
  }
}
